﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Nancy;
using Nancy.Responses.Negotiation;
using QQ2564874169.Core;
using QQ2564874169.Core.Caching;

namespace QQ2564874169.WebFx.Nancy.Web
{
    public abstract class NancyController : NancyModule
    {
        private class SettingContainer : ConcurrentDictionary<string, RouteSetting[]>
        {

        }

        private static readonly SettingContainer RouteSettings = new SettingContainer();

        public static event Action<NancyController> Created;
        public event EventHandler<ActionBeforeEventArgs> ActionBefore;
        public event EventHandler<ActionAfterEventArgs> ActionAfter;
        public event EventHandler<ActionErrorEventArgs> ActionError;
        public RouteSetting ExecutingRoute { get; private set; }
        public TempData TempData { get; private set; }

        protected NancyController()
        {
            TempData = new TempData(this);
            BindAction();
            Created?.Invoke(this);
            
        }

        protected virtual object OnActionBefore(MethodInfo action)
        {
            var e = new ActionBeforeEventArgs
            {
                Action = action
            };
            ActionBefore?.Invoke(this, e);
            return e.Response;
        }

        protected virtual void OnActionAfter(MethodInfo action)
        {
            var e = new ActionAfterEventArgs { Action = action };
            ActionAfter?.Invoke(this, e);
        }

        protected virtual object OnActionError(MemberInfo action, Exception ex)
        {
            var e = new ActionErrorEventArgs
            {
                Ex = ex,
                Action = action
            };
            ActionError?.Invoke(this, e);
            return e.Response;
        }

        protected virtual ICache CreateTempDataContainer()
        {
            return new AppCache();
        }

        protected virtual string OnPath(string path)
        {
            var items = path.TrimStart('/').Split('/').ToArray();
            if (items.Length > 1)
            {
                const string repstr = "controller";
                var ctrl = items[0];
                if (ctrl.EndsWith(repstr, StringComparison.OrdinalIgnoreCase))
                    items[0] = ctrl.Substring(0, ctrl.Length - repstr.Length);
            }
            return "/" + string.Join("/", items);
        }

        protected virtual RouteSetting OnRouteSetting(RouteSetting route)
        {
            if (route.Action.DeclaringType == null)
                throw new ArgumentNullException();

            var dir = route.Action.DeclaringType.Name;
            var attr = route.Action.DeclaringType.GetCustomAttribute<ControllerNameAttribute>();
            if (attr != null)
            {
                dir = attr.Name;
            }
            if (route.Routes.Count < 1)
            {
                var path = ("/" + dir + "/" + route.Action.Name).QQSplit('/').QQJoin("/");
                route.Routes.Add("/" + path);
            }
            for (var i = 0; i < route.Routes.Count; i++)
            {
                var path = route.Routes[i].Trim();

                if (path != "/" && path.EndsWith("/"))
                    path = path.TrimEnd('/');

                if (path.StartsWith("/") == false)
                {
                    path = "/" + dir + "/" + path;
                }
                route.Routes[i] = OnPath(path);
            }
            
            return route;
        }

        protected virtual RouteSetting[] LoadAction(Type controllerType)
        {
            if (controllerType == null) 
                throw new ArgumentNullException(nameof(controllerType));
            var list = new List<RouteSetting>();
            var methods = controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public |
                                                    BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            foreach (var m in methods)
            {
                foreach (var attr in m.GetCustomAttributes<RouteSetting>().ToArray())
                {
                    var item = attr;
                    item.Action = m;
                    item = OnRouteSetting(item);
                    if (item.Routes != null && item.Routes.Count > 0 && item.Action != null)
                    {
                        list.Add(item);
                    }
                }
            }

            return list.ToArray();
        }

        private Func<dynamic, dynamic> CreateFunc(RouteSetting route)
        {
            return p =>
            {
                var action = route.GetAction(Context);
                try
                {
                    ExecutingRoute = route;
                    return OnActionBefore(action) ?? action.Invoke(this, null);
                }
                catch (Exception ex)
                {
                    var err = ex;
                    if (ex is TargetInvocationException && ex.InnerException != null)
                    {
                        err = ex.InnerException;
                    }
                    var resp = OnActionError(action, err);
                    if (resp != null)
                        return resp;
                    throw err;
                }
                finally
                {
                    OnActionAfter(action);
                    ExecutingRoute = null;
                }
            };
        }

        private void BindAction()
        {
            var selfType = GetType();

            if (selfType.FullName == null) return;

            if (RouteSettings.ContainsKey(selfType.FullName) == false)
            {
                var actions = LoadAction(selfType);

                RouteSettings.TryAdd(selfType.FullName, actions);
            }

            foreach (var item in RouteSettings[selfType.FullName])
            {
                var loc = item;
                var action = CreateFunc(loc);
                foreach (var path in loc.Routes)
                {
                    if (loc.Method.HasFlag(RequestMethod.Delete))
                    {
                        Delete[path] = action;
                    }
                    if (loc.Method.HasFlag(RequestMethod.Put))
                    {
                        Put[path] = action;
                    }
                    if (loc.Method.HasFlag(RequestMethod.Post))
                    {
                        Post[path] = action;
                    }
                    if (loc.Method.HasFlag(RequestMethod.Get))
                    {
                        Get[path] = action;
                    }
                }
            }
        }

        protected virtual Negotiator GetView(string path, object model)
        {
            var final = path;
            if (string.IsNullOrEmpty(path))
            {
                final = ExecutingRoute.Action.Name;
            }
            if (final.StartsWith("/") == false)
            {
                final = string.Join("/", GetType().Name, final);
            }
            if (string.IsNullOrEmpty(path))
            {
                final = OnPath(final);
            }
            return View[final, model];
        }

        protected Negotiator GetViewByPath(string path)
        {
            return GetView(path, null);
        }

        protected Negotiator GetViewByModel(object model)
        {
            return GetView(null, model);
        }

        protected Negotiator GetView()
        {
            return GetView(null, null);
        }
    }
}
